/**
 * 回调函数的形状
 */
interface CallbackInterface {
    (data?: any): any
}

interface UniInterface {
    navigateTo: Function
    redirectTo: Function
    reLaunch: Function
    switchTab: Function
    navigateBack: Function
    getEnv: Function
    postMessage: Function
}

declare const uni: UniInterface

interface UniJsBridgeInterFace {
    init: Function,
    send: Function,
    callHandler: Function,
    registerHandler: Function,
    uni: UniInterface
    _handleMessageFromNative: Function,
    _messageHandler?: Function
}

interface HandlersInterFace {
    [index: string]: Function
}

interface NativeToH5Message {
    callbackId?: string // Native端自己需要回调函数是ID，一般由Native端发送给H5，然后H5回传给Native
    responseId?: string // H5端回调函数的ID，一般是由H5发送给Native，然后Native回传给H5
    handlerName?: string    // messageHandlers中的key
    responseData?: string | Object  // Native传给H5的参数
}

/**
 * 回调函数的形状
 * data: 参数
 * callback: 回调函数，执行则可以让对应的端执行回调；比如在H5端调用了Native端的功能，在H5端如果不执行callback()，那么Native
 * 是不会执行回调的(Native自己定义的回调)
 */
interface ResponseCallback {
    (data?: Object | string, callback?: CallbackInterface): any
}


export {}

declare global {
    interface Window {
        UniJsBridge: UniJsBridgeInterFace
    }
}

if (window.UniJsBridge) {
    throw new Error('UniJsBridge已经存在了')
}

let uniqueId: number = 1;   // 全局唯一标识,主要用来生产callbackId
const responseCallbacks: HandlersInterFace = {};   // 给客户端发送消息后的回调函数集合
const messageHandlers: HandlersInterFace = {};     // 在H5中注册的函数（就是等待客户端调用的函数）的集合
let receiveMessageQueue: any = [];   // 消息队列，Native给H5发消息的队列，所有的消息都集中在此，依次调用

if (!uni) {
    throw new Error('uni未定义，需要先引入uni-sdk,地址为：https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js')
}

const UniJsBridge: UniJsBridgeInterFace = {
    init,
    send,
    callHandler,
    registerHandler,
    uni,
    _handleMessageFromNative: _handleMessageFromNative,
}

// Native调用H5函数必须是要挂载到window下
window.UniJsBridge = UniJsBridge

function init(messageHandler: Function) {
    if (UniJsBridge._messageHandler) {
        throw new Error('UniJSBridge._messageHandler 被调用了两次')
    }

    UniJsBridge._messageHandler = messageHandler

    let receiveMessage: any = receiveMessageQueue;
    receiveMessageQueue = null;

    for (let i = 0; i < receiveMessage.length; i++) {
        _dispatchMessageFromNative(receiveMessage[i])
    }
}

/**
 * 发送消息给客户端
 * PS:主动发送
 * @param data 给客户端的数据
 * @param responseCallback 回调函数
 */
function send(data: string | Object, responseCallback: ResponseCallback) {
    _doSend('send', data, responseCallback)
}

// 调用线程
function callHandler(handlerName: string, data: string | Object, responseCallback: ResponseCallback) {
    _doSend(handlerName, data, responseCallback);
}

/**
 * 注册一个函数，待客户端调用
 * PS:被动等待调用
 * @param handlerName
 * @param handler: 接收两个参数，data(客户端回复给H5的数据)，responseCallback(一个回调函数：调用该函数，客户端才可以执行回调，且可以传参数给客户端)
 */
function registerHandler(handlerName: string, handler: ResponseCallback) {
    messageHandlers[handlerName] = handler
}

/**
 * 发送给客户端的核心函数，依赖uni-app SDk
 * PS:主动发送
 * @param handlerName
 * @param message
 * @param responseCallback: 可接收String和Function,如果为String的话就是给客户端的一个回调ID，这个ID其实本来就是由客户端自己传过来的
 * 如果为Function的话就是H5的回调函数
 * @private
 */
function _doSend(handlerName: string, message: Object | string, responseCallback: ResponseCallback | string) {
    let callbackId;

    if (typeof responseCallback === 'string') {
        callbackId = responseCallback;
    } else if (responseCallback) {
        /**
         * 把回调函数塞进集合中去，待客户端代码执行完毕就去找到对应的函数开始执行
         */
        callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
        responseCallbacks[callbackId] = responseCallback;
    } else {
        callbackId = '';
    }

    if (!uni.postMessage) {
        return console.error('此接口依赖uni.webview,具体参考：https://uniapp.dcloud.io/component/web-view')
    }

    // 给客户端发送消息
    uni.postMessage({
        data: {
            data: message,
            callbackId,
            handlerName,
        },
    });
}

function _dispatchMessageFromNative(messageJSON: NativeToH5Message) {
    if (!messageJSON) {
        return console.warn('客户端没有回复任何消息')
    }

    try {
        /**
         * responseId: 客户端回复前端的回调函数的ID
         * callbackId: 给客户端自己用的回调ID
         * handlerName: 前后端统一约定的一个名字[约定不同的名字对应不同的功能，需要后期加，这里只是封装的一个方法]
         * responseData: 客户端给前端的参数
         */
        let {callbackId, responseId, handlerName, responseData} = messageJSON
        let responseCallback

        if (responseId) {
            responseCallbacks[responseId] && responseCallbacks[responseId](responseData)
            delete responseCallbacks[responseId];
        } else {
            if (callbackId) {
                let callbackResponseId = callbackId
                responseCallback = function (responseData: Object | string) {
                    _doSend('response', responseData, callbackResponseId)
                }
            }

            let handler: any = UniJsBridge._messageHandler;
            if (handlerName) {
                handler = messageHandlers[handlerName]
            }
            handler(responseData, responseCallback)
        }
    } catch (e) {
        console.log(e)
    }
}

/**
 * 提供给客户端调用的函数，
 * 约定客户端只能通过此函数发消息给H5
 * @param messageJSON
 * @private
 */
function _handleMessageFromNative(messageJSON: NativeToH5Message) {
    if (receiveMessageQueue) {
        receiveMessageQueue.push(messageJSON)
    }
    _dispatchMessageFromNative(messageJSON)
}

